//
// Created by wang on 2021/7/1.
//

#include "dev.h"
#include "cmn.h"
#include "sys.h"
#include "netutil.h"
#include "mp.h"

#define MIN(x,y) ((x) <= (y) ? (x) : (y))

dev_info g_dev_info;
pkt_analyzer g_pkt_analyzer;

static void init_dev_ports();

_Noreturn static void* port_pend(void *);
static void init_port_cache(uint8_t);
_Noreturn static void* analyzer(void *);
static ret_t strip_n(
        pkt_cache *pkt_cache, uint32_t block_n, uint32_t c_n, uint32_t n);
static void init_pkt_analyzer();

inline static uint8_t is_cache_empty(pkt_cache *cache)
{return (cache->blk_w == cache->blk_r);}

inline static uint8_t is_cache_full(pkt_cache *cache)
{return ((cache->blk_w + 1) % cache->blk_n == cache->blk_r);}

/* Must be called at system start-up */
void dev_init() {
    memset(&g_dev_info, 0, sizeof g_dev_info);
    memcpy(g_dev_info.ip.ip_str, "10.10.10.1", sizeof(g_dev_info.ip.ip_str));
    pil_inet_pton(AF_INET, g_dev_info.ip.ip_str, &g_dev_info.ip.ip);

    init_dev_ports();
    init_pkt_analyzer();
}

static void init_pkt_analyzer() {
    ret_t ret = ENET_RET_SUCCESS;

    g_pkt_analyzer.pkt_buf_sz = DEV_PKT_BUFFER_BLOCK_SIZE;
    g_pkt_analyzer.pkt_buf_n = DEV_PKT_BUFFER_BLOCK_NUM;
    ret = mp_create( g_pkt_analyzer.pkt_buf_sz, g_pkt_analyzer.pkt_buf_n, 0,
            &g_pkt_analyzer.pool_id);

    assert(ret == ENET_RET_SUCCESS);
    pthread_create(&g_pkt_analyzer.tid, NULL, analyzer, NULL);
}


static ret_t strip_n(
        pkt_cache *pkt_cache, uint32_t block_n, uint32_t c_n, uint32_t n) {


}

_Noreturn static void* analyzer(void *param) {
    uint8_t p_idx = 0;
    pkt_cache *rx =  NULL;
    cache_info *cache = NULL;
    uint32_t pre_n = 0;
    uint16_t pkt_len = 0;
    uint32_t c_idx = 0;

    while (1) {
        for (p_idx = 0; p_idx < g_dev_info.port_num; p_idx++) {
            rx = &g_dev_info.ports[p_idx].rx_cache;
            if (!is_cache_empty(rx)) {
                cache = &rx->cache_info[rx->blk_r];
                for (c_idx = 0; c_idx < cache->len; c_idx++) {
                    /* TODO: how to analyzer... */
                    switch (cache->cache[c_idx]) {
                        case 0x55:
                            pre_n++;
                            break;
                        case 0xD5:
                            if (pre_n == 7) {
                                /* packet detected. */
                                pkt_len = ntohs(*(uint16_t*)
                                        &cache->cache[c_idx]);


                                uint32_t gap, n_block;
                                pkt_buffer buf;
                                uint8_t *p = buf.pkt;
                                uint32_t t = 0;
                                gap = cache->len - c_idx - 1;
                                n_block = (pkt_len - gap) % rx->blk_sz;
                                assert(mp_alloc_n(g_pkt_analyzer.pool_id,
                                                     &buf.pkt,
                                           pkt_len / g_pkt_analyzer
                                           .pkt_buf_sz + 1) ==
                                           ENET_RET_SUCCESS);
                                memcpy(p, &cache->cache[c_idx], gap);
                                rx->blk_r++;
                                asm volatile("" ::: "memory"); /*memory barrier for loop-cache */
                                cache = &rx->cache_info[rx->blk_r];
                                p += gap;
                                pkt_len -= gap;
                                while (n_block--) {
                                    t = MIN(rx->blk_sz, pkt_len);
                                    memcpy(p, &cache->cache, t);
                                    pkt_len -= t;
                                }
                            }
                            break;
                        default:
                            pre_n = 0;
                            break;
                    }
                }
            }
        }
    }
}

static void init_dev_ports() {
    mac mac;
    uint32_t pid, ret = ENET_RET_SUCCESS, p_idx = 0;

    pid = pil_getpid();
    ret = pil_device_mac(mac);

    if (ENET_RET_SUCCESS != ret) {
        /* TODO: should optimized err check. */
        printf("dev_init failed: %s\n", err_to_str(ret, NULL));
        fflush(stdout);
        exit(-1);
    }
    /* TODO: port num should be initialized by config file. */
    g_dev_info.port_num = 1;
    g_dev_info.ports = (dev_port *) malloc(
            g_dev_info.port_num * sizeof(dev_port));
    assert(g_dev_info.ports);
    if (g_dev_info.ports) {
        for (p_idx = 0; p_idx < g_dev_info.port_num; p_idx++) {

            /*TODO: port index should from 0 or 1?*/
            g_dev_info.ports[p_idx].id = p_idx;

            snprintf(g_dev_info.ports[p_idx].pp.uni_name, DEV_MAX_PIP_NAME_LEN,
                     "%d%02x%02x%02x%02x%02x%02x%d",
                     pid, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
                     p_idx);

            g_dev_info.ports[p_idx].state = DEV_PORT_DOWN;
            pthread_create(&g_dev_info.ports[p_idx].tid, NULL, port_pend,
                           &g_dev_info.ports[p_idx]);
            init_port_cache(p_idx);
        }

//        for (p_idx = 0; p_idx < g_dev_info.port_num; p_idx++) {
//            pthread_join(g_dev_info.ports[p_idx].tid, NULL);
//            /* TODO: port thread exit, log this error. */
//        }
    }
}

static void init_port_cache(uint8_t port_num) {

    int32_t i = 0;
    dev_port *port = (dev_port* )&g_dev_info.ports[port_num];
    ret_t ret;
    pkt_cache *rx = &port->rx_cache;
    pkt_cache *tx = &port->tx_cache;

    rx->blk_sz = DEV_PKT_CACHE_BLOCK_SIZE;
    rx->blk_n = DEV_PKT_CACHE_BLOCK_NUM;
    ret = mp_create(rx->blk_sz,
              rx->blk_n, 0, &rx->pool_id);


    assert(ret == ENET_RET_SUCCESS);
    tx->blk_sz = DEV_PKT_CACHE_BLOCK_SIZE;
    tx->blk_n = DEV_PKT_CACHE_BLOCK_NUM;
    ret = mp_create(tx->blk_sz,
                    tx->blk_n, 0, &tx->pool_id);
    assert(ret == ENET_RET_SUCCESS);

    assert(mp_alloc_n(rx->pool_id, &rx->cache_info[i].cache, rx->blk_n)
        == ENET_RET_SUCCESS);
    assert(mp_alloc_n(tx->pool_id, &tx->cache_info[i].cache, tx->blk_n)
        == ENET_RET_SUCCESS);

    rx->blk_r = rx->blk_w = tx->blk_r = tx->blk_w = 0;
}

_Noreturn static void* port_pend(void *port_info) {
    pil_pipe_handle handle;
    dev_port *port = (dev_port*)port_info;
    uint32_t ret = ENET_RET_SUCCESS;
    unsigned long read_len = 0;
    unsigned long write_len = 0;
    pkt_cache *rx = &port->rx_cache;
    pkt_cache *tx = &port->tx_cache;
    while (1)
    {
        ret = pil_connect_pipe(port->pp.uni_name, PIL_PIPE_RW, &handle);
        // Break if the pipe handle is valid.

        // Exit if an error other than ERROR_PIPE_BUSY occurs.
        if (ret != ENET_RET_SUCCESS)
        {
            if (ret == ERR_PIL_PIPE_NOT_FOUND)
            {
                pil_sleep(100);
                continue;
            } else {
                /* TODO: should log this error */
                abort();
            }
        }
        port->state = DEV_PORT_UP;
        while (1) {
            /* TODO: the port is connected to the other device's port now.
             * wait port's or protocol layer's data to write ... */
            if (!is_cache_full(rx)) {

                ret = pil_read_pipe(
                        &handle, rx->cache_info[rx->blk_w].cache, rx->blk_sz,
                        &read_len);
                asm volatile("" ::: "memory"); /*memory barrier for loop-cache */
                if (ret > 0) {
                    rx->cache_info[rx->blk_w].len = read_len;
                    rx->blk_w ++;
                }
            }

            if (!is_cache_empty(tx)) {
                ret = pil_write_pipe(
                        &handle, tx->cache_info[tx->blk_r].cache,
                        tx->cache_info[tx->blk_r].len, &write_len);
                asm volatile("" ::: "memory"); /*memory barrier for loop-cache */
                if (ret > 0) {
                    tx->blk_w ++;
                }
            }
        }

    }

}

void de_init_dev() {
    free(g_dev_info.ports);
    memset(&g_dev_info, 0, sizeof g_dev_info);
}

void get_dev_ip_node(ip_node *ip_node_buf) {
    memcpy(ip_node_buf, &g_dev_info.ip, sizeof(ip_node));
}

void log_dev_info() {
    uint32_t pid = pil_getpid();
}
